package net.wanderinghorse.jsonmessage;

import net.wanderinghorse.json.*;
import net.wanderinghorse.jsonmessage.poster.HttpRequestPoster;
import net.wanderinghorse.jsonmessage.poster.LocalRequestPoster;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class UI extends JFrame {
    private static final String AppTitle = "JSONMessage Test/Demo UI v20100613";
    private int indention = 4;
    private JButton btnSubmit;
    private JButton btnQuit;
    private JTextArea taReq;
    private JTextArea taRes;
    private final Boolean mutexRequest = true;
    private final Boolean mutexResponse = true;
    private RequestPoster posterLocal = new LocalRequestPoster();
    private RequestPoster posterRemote = new HttpRequestPoster();
    private OptionsPanel pnlOpt;
    private RequestHandler onResponse = new OnResponse();
    private CallbackRequestOpt onerror = new OnError();
    private CallbackRequestOpt beforePost = new BeforePost();
    private CallbackRequestOpt afterPost = new AfterPost();
    private JLabel statusBar;
    private JCheckBox cbPostOnSelect;
    private JTextField tfProxyServer;
    private JSpinner spinProxyPort;

    private static JSONObject config;
    private static final String LocalDispName = "local";

    private class OnResponse implements RequestHandler {
        public void processResponse(final Request req, final Response resp) {
            setResponseText( resp );
        }
    }
    private class BeforePost implements CallbackRequestOpt {
        public void invoke(final Request r, final PostOptions opt ) {
            // not working? btnSubmit.setEnabled( false );
            // Any changes i make to swing elements are not activating
            // here.
            setStatusMessage( "Posting '"+r.getType()+"' message to ["
                +opt.getUrl()+"]...");
        }
    }
    private class AfterPost implements CallbackRequestOpt {
        public void invoke(final Request r, final PostOptions opt ) {
            // not working during event handling: btnSubmit.setEnabled( true );
            setStatusMessage( "Finished post() of ["+r.getType()+"] message to ["+opt.getUrl()+"].");
        }
    }
    private class OnError implements CallbackRequestOpt {
        public void invoke(final Request r, final PostOptions opt ) {
            StringBuilder sb = new StringBuilder();

            try {
                sb.append("onError() handler:\n")
                    .append( "Request:\n")
                    .append(r.toJSON(indention))
                    .append("\n\n")
                    .append("Connection Options:\n")
                    .append(opt.toJSON(indention))
                ;
            }
            catch( JSONException ex ) {
                // i can't believe StringBuilder() doesn't have a clear() member.
                sb = new StringBuilder();
                sb.append("EXCEPTION:\n").append(ex.toString());
            }
            setResponseText( sb.toString() );
        }
    }


    private class OptionsPanel extends JPanel {
        JComboBox cbDispatcher;
        JPanel pnlDispatcher;
        OptionsPanel() {
            this.setLayout( new GridBagLayout() );
            JPanel pnl = new JPanel();
            pnlDispatcher = pnl;
            pnl.setBorder( BorderFactory.createTitledBorder("Dispatcher") );
            cbDispatcher = new JComboBox();
            cbDispatcher.setEditable( true );
            pnl.add( cbDispatcher );

            GridBagConstraints con = new GridBagConstraints();
            con.gridx = 0;
            con.gridy = 0;
            con.weightx = 1.0;
            con.weighty = 1.0;
            con.fill = GridBagConstraints.HORIZONTAL;
            this.add( pnl , con );

            JSONArray ar = null;
            if( UI.loadConfig() ) {
                ar = config.optJSONArray( "dispatcherList");
            }
            if( null == ar ) {
                ar = new JSONArray();
                ar.put( "http://jsonmessage/lib/JSONMessage/JSONRequest-dispatch.php" );
                ar.put( "http://localhost:8080/JSONMessage/dispatch" );
                ar.put( "http://fossil.wanderinghorse.net/demos/JSONMessage/lib/JSONMessage/JSONRequest-dispatch.php" );
            }
            cbDispatcher.insertItemAt( UI.LocalDispName, 0 );
            cbDispatcher.setSelectedIndex(0);
            {
                int i;
                int ndx = 1;
                Object o;
                for( i = 0; i < ar.length(); ++i, ++ndx ) {
                    try {
                       o = ar.get(i);
                    }
                    catch( Exception ex ) {
                        continue;
                    }
                    if( null == o ) continue;
                    cbDispatcher.insertItemAt( o.toString(), ndx );
                }
            }

            pnl = new JPanel();
            pnl.setToolTipText( "Not yet implemented." );
            pnl.setBorder( BorderFactory.createTitledBorder("HTTP Proxy") );
            loadConfig();

            if( true ) {
                JSONObject proxyConf = (config == null) ? null : config.optJSONObject("httpProxy");
                pnl.setLayout( new GridBagLayout() );
                tfProxyServer = new JTextField();
                tfProxyServer.setToolTipText( "Not yet honored." );
                tfProxyServer.setEnabled( false );
                if( null != proxyConf ) {
                    String srv = proxyConf.optString("host", null);
                    if( (null != srv) && !srv.isEmpty() && !srv.equals("null") ) {
                        tfProxyServer.setText( srv );
                    }
                }
                SpinnerNumberModel smod = new SpinnerNumberModel();
                smod.setMinimum( 0 );
                smod.setMaximum( 32764 );
                smod.setValue( (null == proxyConf)
                        ? 0
                        : proxyConf.optInt("port",0)
                );
                spinProxyPort = new JSpinner( smod );
                con = new GridBagConstraints();
                con.gridx = 0;
                con.gridy = 0;
                con.weighty = 1;
                //con.fill = GridBagConstraints.BOTH;
                pnl.add( new JLabel("Proxy Server:"), con);

                con = (GridBagConstraints) con.clone();
                con.gridx = 1;
                con.weightx = 5.0;
                con.fill = GridBagConstraints.HORIZONTAL;
                pnl.add( tfProxyServer, con  );

                con = (GridBagConstraints) con.clone();
                con.gridx = 2;
                con.gridy = 0;
                con.weightx = 1;
                pnl.add( new JLabel("Proxy Port:"), con );

                con = (GridBagConstraints) con.clone();
                con.gridx = 3;
                con.weightx = 1;
                //con.fill = GridBagConstraints.HORIZONTAL;
                pnl.add( spinProxyPort, con  );
            }
            con = new GridBagConstraints();
            con.gridx = 0;
            con.gridy = 1;
            con.weightx = 1;
            con.weighty = 1;
            con.fill = GridBagConstraints.HORIZONTAL;
            this.add( pnl , con );
        }
    }

    private static boolean loadConfig() {
        if(null != config) return true;
        synchronized (UI.class) {
            config = JSONUtil.getConfigObject(UI.class);
            return null != config;
        }
    }

    /**
     * Creates a JPanel object which has the given label and contains
     * the given component (which is itself contained in a JScrollPane).
     * The lbl may be null but ta may not.
     *
     * The main intention is that comp be-a JTextArea, but that's not
     * strictly necessary.
     * 
     * @param lbl The label for the returned pane (may be null).
     * @param comp The component to wrap in a scrollpane.
     * @return The containing panel.
     */
    public static JPanel createScrollablePanel( String lbl, Component comp ) {
        JScrollPane scroller = new JScrollPane(comp);
        JPanel pnl = new JPanel();
        if( null != lbl ) {
            pnl.setBorder( BorderFactory.createTitledBorder(lbl) );
        }
        GridBagLayout lay = new GridBagLayout();
        GridBagConstraints cs = new GridBagConstraints();
        cs.weightx = 10.0;
        cs.weighty = 10.0;
        cs.fill = GridBagConstraints.BOTH;
        pnl.setLayout( lay );
        pnl.add( scroller, cs );
        return pnl;
    }

    /**
     * Sets the contents of this.taRes.
     * @param s Text to show.
     */
    private void setResponseText( String s ) {
        synchronized ( this.mutexResponse ) {
            this.taRes.setText( s );
        }
    }
    private void setRequestText( String s ) {
        synchronized ( this.mutexRequest ) {
            this.taReq.setText( s );
        }
    }
    private void setRequestText( final JSONable j ) {
        try {
            this.setRequestText( j.toJSONObject().toString(this.indention) );
        }
        catch(Exception ex ) {

            this.setRequestText( "EXCEPTION:\n"+ex.toString() );
        }
    }
    private void setResponseText( final JSONable j ) {
        try {
            this.setResponseText( j.toJSONObject().toString(this.indention) );
        }
        catch(Exception ex ) {
            this.setResponseText( "EXCEPTION:\n"+ex.toString() );
        }
    }
    /**
     * Sets up the UI components.
     */
    private void initUI()
    {
        JPanel contentPane;
        JPanel pnlButtons;
        JPanel pnlRR;
        JPanel pnlReq;
        JPanel pnlRes;
        this.setTitle(AppTitle);
        {
            taReq = new JTextArea();
            pnlReq = createScrollablePanel( "Request", taReq );

            taRes = new JTextArea();
            taRes.setEditable(false);
            pnlRes = createScrollablePanel( "Response", taRes );

//            final int cols = 40;
//            final int rows = 20;
//            taReq.setColumns( cols );
//            taReq.setRows( rows );
//            taRes.setColumns( cols );
//            taRes.setRows( rows );
        }

        {
            pnlRR = new JPanel();
            pnlRR.setLayout( new CardLayout() );
            JSplitPane split = new JSplitPane();
            split.setOneTouchExpandable( true );
            split.setLeftComponent( pnlReq );
            split.setRightComponent( pnlRes );
            pnlRR.add("ReqRes", split);
        }

        pnlButtons = new JPanel();
        {
            pnlButtons.setLayout( new FlowLayout() );
            this.btnSubmit = new JButton();
            btnSubmit.setText("Submit");
            this.btnQuit = new JButton("Quit");
            pnlButtons.add( btnSubmit );
            pnlButtons.add( btnQuit );
        }


        cbPostOnSelect = new JCheckBox("Post on select?",true);
        cbPostOnSelect.setToolTipText("If checked, messages are immediately posted as soon as they are selected.");
        final JComboBox combo = new JComboBox();
        {  // Set up combo box/selection list.
            combo.setEditable( true );
            java.util.List<String> li = new java.util.ArrayList<String>();
            /** TODO: find a way to get this list via reflection of available classes.
             */
            if( loadConfig() ) {
                JSONArray ar = config.optJSONArray( "requestList");
                if( null != ar ) {
                    int i;
                    Object o;
                    for( i = 0; i < ar.length(); ++i ) {
                        try {
                           o = ar.get(i);
                        }
                        catch( Exception ex ) {
                            continue;
                        }
                        if( null == o ) continue;
                        li.add( o.toString() );
                    }
                }
            }
            else {
                li.add( "ping" );
                li.add( "ChuckNorris" );
            }
            for( String x : li ) {
                combo.addItem(x);
            }
            combo.addActionListener( new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    try {
                        Object o = combo.getSelectedItem();
                        if( null != o ) {
                            Request r = new Request();
                            r.setType( o.toString() );
                            setRequestText( r );
                            if( cbPostOnSelect.isSelected() ) {
                                btnSubmit.doClick();
                            }
                        }
                    }
                    catch( Exception ex ) {
                        setRequestText( "EXCEPTION:\n"+ex.toString() );
                    }
                }
            });
        }
        JPanel pnlCombo = new JPanel();
        {
            FlowLayout lay = new FlowLayout();
            lay.setAlignment( FlowLayout.LEFT );
            pnlCombo.setLayout( lay );
            pnlCombo.add( new Label("Request type:"));
            pnlCombo.add( combo );
            pnlCombo.add( cbPostOnSelect );
        }

        contentPane = new JPanel();
        contentPane.setLayout( new BorderLayout() );
        this.setContentPane( contentPane );
        JTabbedPane tabs = new JTabbedPane();
        contentPane.add(tabs);

        pnlOpt = new OptionsPanel();
        {
            JPanel tab = new JPanel();

            GridBagLayout gr = new GridBagLayout();
            tab.setLayout( gr );
            GridBagConstraints cs = new GridBagConstraints();
            cs.weightx = 10.0;
            cs.weighty = 10.0;
            cs.fill = GridBagConstraints.BOTH;
            tab.add( pnlRR, cs );

            int y = 1;
            cs = new GridBagConstraints();
            cs.weightx = 1.0;
            cs.gridy = y++;
            cs.fill = GridBagConstraints.HORIZONTAL;
            tab.add( pnlCombo, cs );

            cs = new GridBagConstraints();
            cs.weightx = 1.0;
            cs.gridy = y++;
            cs.fill = GridBagConstraints.HORIZONTAL;
            tab.add( pnlOpt.pnlDispatcher, cs );

            cs = new GridBagConstraints();
            cs.weightx = 1.0;
            cs.gridy = y++;
            cs.fill = GridBagConstraints.HORIZONTAL;
            tab.add( pnlButtons, cs );


            statusBar = new JLabel();
            cs = new GridBagConstraints();
            cs.weightx = 1.0;
            cs.gridy = y++;
            cs.fill = GridBagConstraints.HORIZONTAL;
            tab.add( statusBar, cs );            

            tabs.add( "Request/Response", tab );
        }

        {
            tabs.add("Options", pnlOpt);
            tabs.setEnabledAt(1, false);
        }
        {
            JSONObject jo = JSONMessage.getConfig();
            if( null != jo ) {
                this.indention = jo.optInt( "indentation", 4 );
            }
        }


    }

    public UI() {
        this.initUI();
        //setContentPane(contentPane);
        //getRootPane().setDefaultButton(btnSubmit);

//        this.rrf = new RequestResponseForm();
//        pnlRR.add( rrf.getContainer(), "RRView" );

        btnSubmit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });
        {
            /** It took me a fucking hour to figure out how to
             * do this...
             */
            Action act = new AbstractAction() {
                public void actionPerformed(ActionEvent e) {
                    onOK();
                }
            };
            act.putValue( AbstractAction.NAME, "Submit");
            act.putValue( AbstractAction.MNEMONIC_KEY, KeyEvent.VK_S );
            btnSubmit.setAction(act);
        }

        btnQuit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        setDefaultCloseOperation(DISPOSE_ON_CLOSE);//EXIT_ON_CLOSE);//DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });
        try {
            Request r = new Request();
            r.setType("ping");
            r.setPayload("Hi, world!");
            this.setRequestText( r.toJSON(4) );
            btnSubmit.doClick();
        }
        catch(Exception e)
        {
            this.setResponseText("EXCEPTION:\n"+e.toString());
        }
        this.pack();
    }

    private PostOptions createPostOptions() {
        PostOptions po = PostOptions.createDefault();
        Object sel = pnlOpt.cbDispatcher.getSelectedItem();
        String disp = null;
        if( null != sel ) {
            disp = sel.toString();
        }
        if( (null == disp) || disp.isEmpty() ) disp = LocalDispName;
        return po.setOnResponse( this.onResponse )
            .setOnError( this.onerror )
            .setBeforePost( this.beforePost )
            .setAfterPost( this.afterPost )
            .setUrl( disp )
            //.setAsync( true ) // TODO: add toggle
            ;
    }

    protected void setStatusMessage( final String s ) {
        synchronized ( this ) {
            this.statusBar.setText( s );
        }
    }

    private void doPost() {
        Object sel = pnlOpt.cbDispatcher.getSelectedItem();
        String disp = LocalDispName;

        if( null != sel ) {
            disp = sel.toString();
        }
        RequestPoster poster = LocalDispName.equals(disp) ? posterLocal : posterRemote;
        PostOptions po = this.createPostOptions();
        try
        {
            Request req = new Request( this.taReq.getText() );
            poster.post( req, po );
        }
        catch( Exception ex ) {
           this.setResponseText( "EXCEPTION: "+ex );
        }
    }
    private void onOK() {
        /**
            Any UI (e.g. this.statusMessage) changes i make to swing elements during the
            event handling is not showing up until after the event returns.
         */
        setStatusMessage( "Posting request...");
        this.doPost();
//        try
//        {
//            Request req = new Request( this.taReq.getText() );
//            Response res = ResponseLoader.instance().loadRequestHandler( req );
//            if( null == res ) {
//               this.setResponseText( "Could not find Response class for request:\n"
//                                     +req.toJSON(this.indention) );
//            }
//            else {
//                res.processRequest( req );
//                this.setResponseText( res );
//            }
//        }
//        catch( Exception ex ) {
//            this.taRes.setText( "EXCEPTION: "+ex );
//        }
    }

    private void onCancel() {
        dispose();
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    UI dialog = new UI();
                    dialog.setVisible(true);
                }
                catch( Exception e ) {
                    e.printStackTrace(System.out);
                    System.exit(1);
                }
            }});
    }

}
